﻿
using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using SWF;

namespace SWF.Runtime
{

    //Abstract the renderer

    //This class should only really be a collection of Meshes to render with references to materials

    public class Shape : IRenderable
    {
        //----------------------------------------------------------------------
        private uint _Id;
        private Rectangle _BoundingBox;

        private List<CurvePoint> _ShapeGeometry;    //Dont use this!!!

        //Store Record tags here????

        //----------------------------------------------------------------------
        private Vector2 _Cursor; //this is only needed for generating, maybe have a factory?

        //----------------------------------------------------------------------
        private List<VertexBuffer> _ShapeVertexBufferList;  
        private List<IndexBuffer> _ShapeIndexBufferList;

        private List<VertexBuffer> _LineVertexBufferList;
        private List<IndexBuffer> _LineIndexBufferList;

        //----------------------------------------------------------------------
        private Effect _Effect;
        private Microsoft.Xna.Framework.Matrix _VPMatrix;   //This shouldn't be here
        private int _ShapeIndexCount; 
        private int _LineIndexCount;

        //----------------------------------------------------------------------
        private Dictionary<uint, FillStyle> _FillStyleList;
        private Dictionary<uint, LineStyle> _LineStyleList;

        //Handling fillStyle 0 and 1 correctly 

        private Color _FillStyle0;
        private Color _FillStyle1;

        private bool _IsGenerated;

        private bool _HasLine;
        private bool _HasShape;

        //----------------------------------------------------------------------
        public uint Id { get { return _Id; } }
        public bool Generated { get { return _IsGenerated; } }

        //----------------------------------------------------------------------
        public Shape(ISwfTag tag)
        {
            dynamic shapeTag = tag; //don't use dynamic

            _Id = shapeTag.ShapeId;
            _BoundingBox = new Rectangle(
                shapeTag.ShapeBounds.XMin, shapeTag.ShapeBounds.YMin,
                shapeTag.ShapeBounds.XMax - shapeTag.ShapeBounds.XMin,
                shapeTag.ShapeBounds.YMax - shapeTag.ShapeBounds.YMin);

            _ShapeGeometry = new List<CurvePoint>();

            _ShapeVertexBufferList = new List<VertexBuffer>();
            _ShapeIndexBufferList = new List<IndexBuffer>();

            _LineVertexBufferList = new List<VertexBuffer>();
            _LineIndexBufferList = new List<IndexBuffer>();

            _FillStyleList = new Dictionary<uint, FillStyle>();
            _LineStyleList = new Dictionary<uint, LineStyle>();
            _Cursor = Vector2.Zero;

            _Effect = SWFMain.SVGShader.Clone();

            _Effect.CurrentTechnique = _Effect.Techniques["SWF8BitmapFill"];

            _FillStyle0 = _FillStyle1 = Color.Red;
            
            _VPMatrix = new Microsoft.Xna.Framework.Matrix(
                1f, 0f, 0f, 0f, 
                0f, -1f, 0f, 0f, 
                0f, 0f, -1f, 0f, 
                0f, 0f, 0f, 1f) 
                * Microsoft.Xna.Framework.Matrix.CreateOrthographicOffCenter(0, SWFMain.Device.Viewport.Width * 20, SWFMain.Device.Viewport.Height * -20, 0, 0, 1);

            foreach (ILineStyle style in shapeTag.Shapes.LineStyles.LineStyles)
            {
                _ParseLineStyle(style);
            }

            foreach (IFillStyle style in shapeTag.Shapes.FillStyles.FillStyles)
            {
                _ParseFillStyle(style);
            }
                
            _ParseShapeRecords(shapeTag.Shapes.ShapeRecords);

            _IsGenerated = false;

            //_GenerateShapeGeometry();
            _GenerateLineGeometry();

            _IsGenerated = _HasLine || _HasShape;
        }

        //----------------------------------------------------------------------
        public void Render(Matrix matrix)
        {
            if (!_IsGenerated
                || !SWF.Debug.ShapeRenderControl.IsEnabled(_Id))
            {
                return;
            }

            SWFMain.Device.RasterizerState = new RasterizerState
            {
                FillMode = FillMode.WireFrame,
                CullMode = CullMode.None
            };

            SWFMain.Device.BlendState = BlendState.AlphaBlend;

            _Effect.Parameters["WVPMatrix"].SetValue(
                Microsoft.Xna.Framework.Matrix.CreateScale(new Vector3(matrix.ScaleX, matrix.ScaleY, 1f))
                * Microsoft.Xna.Framework.Matrix.CreateTranslation(new Vector3(matrix.TranslateX, matrix.TranslateY, 0f))
                * _VPMatrix);

            if (_HasShape)
            {
                _Effect.Parameters["ZDepth"].SetValue(0.01f);
                for (int lPass = 0; lPass < _Effect.CurrentTechnique.Passes.Count; ++lPass)
                {
                    _Effect.CurrentTechnique.Passes[lPass].Apply();

                    for (int lBufferIndex = 0; lBufferIndex < _ShapeIndexBufferList.Count; ++lBufferIndex)
                    {
                        SWFMain.Device.Indices = _ShapeIndexBufferList[lBufferIndex];
                        SWFMain.Device.SetVertexBuffer(_ShapeVertexBufferList[lBufferIndex]);
                        SWFMain.Device.DrawIndexedPrimitives(Microsoft.Xna.Framework.Graphics.PrimitiveType.TriangleList, 0, 0, _ShapeIndexCount, 0, _ShapeIndexCount / 3);
                    }
                }
            }

            if (_HasLine)
            {
                _Effect.Parameters["ZDepth"].SetValue(0f);
                for (int lPass = 0; lPass < _Effect.CurrentTechnique.Passes.Count; ++lPass)
                {
                    _Effect.CurrentTechnique.Passes[lPass].Apply();

                    for (int lBufferIndex = 0; lBufferIndex < _ShapeIndexBufferList.Count; ++lBufferIndex)
                    {
                        SWFMain.Device.Indices = _LineIndexBufferList[lBufferIndex];
                        SWFMain.Device.SetVertexBuffer(_LineVertexBufferList[lBufferIndex]);
                        SWFMain.Device.DrawIndexedPrimitives(Microsoft.Xna.Framework.Graphics.PrimitiveType.TriangleList, 0, 0, _LineIndexCount, 0, _LineIndexCount / 3);
                    }
                }
            }
        }

        //----------------------------------------------------------------------
        private void _GenerateShapeGeometry()
        {
            if (_ShapeGeometry.Count < 3)
            {
                return;
            }

            List<Vertex> lShapePointArray = new List<Vertex>();

            //Level of detail should get applied here

            uint lCurrentFillStyle = 0;

            for (int i = 0; i < _ShapeGeometry.Count; ++i)
            {
                FillStyle lStyle = new FillStyle { Color = Color.Pink };

                if (lCurrentFillStyle != _ShapeGeometry[i].FillStyle)
                {
                    lCurrentFillStyle = _ShapeGeometry[i].FillStyle;
                    if (_FillStyleList.ContainsKey(lCurrentFillStyle))
                    {
                        lStyle = _FillStyleList[lCurrentFillStyle];
                    }

                    //next sub-mesh

                    if (lShapePointArray.Count > 0)
                    {

                        lShapePointArray.Clear();
                    }
                }

                lShapePointArray.Add(new Vertex
                {
                    Position = _ShapeGeometry[i].Point,
                    Color = lStyle.Color.ToVector4(),
                });
            }

            VertexBuffer lVertexBuffer = new VertexBuffer(SWFMain.Device, new VertexDeclaration(Vertex.VertexElements), lShapePointArray.Count, BufferUsage.None);
            lVertexBuffer.SetData(lShapePointArray.ToArray());

            _ShapeVertexBufferList.Add(lVertexBuffer);

            //Decompositon of Shape to produce indices
            List<short> indices = new List<short>();
            short[] t = SWF.Decomposition.FlipcodeDecomposer.ConvexPartition(lShapePointArray);
            if (t.Length < 1)
            {
                t = SWF.Decomposition.TimDecomposer.Decompose(lShapePointArray);
            }
            indices.AddRange(t);

            IndexBuffer lIndexBuffer = new IndexBuffer(SWFMain.Device, IndexElementSize.SixteenBits, indices.Count, BufferUsage.None);
            lIndexBuffer.SetData(indices.ToArray());

            _ShapeIndexBufferList.Add(lIndexBuffer);

            _ShapeIndexCount = indices.Count;

            _HasShape = true;
        }

        //----------------------------------------------------------------------
        private void _GenerateLineGeometry()
        {
            if (_ShapeGeometry.Count < 3)
            {
                return;
            }

            Vector2 n;
            Vector2 t;
            LineStyle style;
            List<Vertex> linePointArray = new List<Vertex>();

            for (int i = 1; i < _ShapeGeometry.Count; ++i)
            {
                if (_ShapeGeometry[i].LineStyle != 0)
                {
                    n = Vector2.Normalize(_ShapeGeometry[i].Point - _ShapeGeometry[i - 1].Point);
                    t = Vector2.Normalize(new Vector2(-n.Y, n.X)) * 0.5f;   //shader uses radius not diameter
                    style = _LineStyleList[_ShapeGeometry[i].LineStyle];

                    linePointArray.Add(new Vertex(_ShapeGeometry[i - 1].Point - (t * style.Width), style.Color));
                    linePointArray.Add(new Vertex(_ShapeGeometry[i - 1].Point + (t * style.Width), style.Color));
                    linePointArray.Add(new Vertex(_ShapeGeometry[i].Point - (t * style.Width), style.Color));
                    linePointArray.Add(new Vertex(_ShapeGeometry[i].Point + (t * style.Width), style.Color));
                }
            }

            if (_ShapeGeometry[0].LineStyle != 0)
            {
                n = Vector2.Normalize(_ShapeGeometry[0].Point - _ShapeGeometry[_ShapeGeometry.Count - 1].Point);
                t = Vector2.Normalize(new Vector2(-n.Y, n.X)) * 0.5f;
                style = _LineStyleList[_ShapeGeometry[0].LineStyle];

                linePointArray.Add(new Vertex(_ShapeGeometry[_ShapeGeometry.Count - 1].Point - (t * style.Width), style.Color));
                linePointArray.Add(new Vertex(_ShapeGeometry[_ShapeGeometry.Count - 1].Point + (t * style.Width), style.Color));
                linePointArray.Add(new Vertex(_ShapeGeometry[0].Point - (t * style.Width), style.Color));
                linePointArray.Add(new Vertex(_ShapeGeometry[0].Point + (t * style.Width), style.Color));
            }

            if (linePointArray.Count > 0)
            {
                VertexBuffer lVertexBuffer = new VertexBuffer(SWFMain.Device, new VertexDeclaration(Vertex.VertexElements), linePointArray.Count, BufferUsage.None);
                lVertexBuffer.SetData(linePointArray.ToArray());
                _LineVertexBufferList.Add(lVertexBuffer);

                List<short> indices = new List<short>();
                for (int i = 0; i < linePointArray.Count; ++i)
                {
                    indices.Add((short)(i * 4)); indices.Add((short)(i * 4 + 1)); indices.Add((short)(i * 4 + 2));
                    indices.Add((short)(i * 4 + 2)); indices.Add((short)(i * 4 + 3)); indices.Add((short)(i * 4 + 1));
                }

                IndexBuffer lIndexBuffer = new IndexBuffer(SWFMain.Device, IndexElementSize.SixteenBits, indices.Count, BufferUsage.None);
                lIndexBuffer.SetData(indices.ToArray());
                _LineIndexBufferList.Add(lIndexBuffer);

                _LineIndexCount = indices.Count;

                _HasLine = true;
            }
            else
            {
                _HasLine = false;
            }
        }

        #region PARSING

        //----------------------------------------------------------------------
        private void _ParseShapeRecords(List<IRecord> lShapeRecordList)
        {
            List<Vertex> lShapePointArray = new List<Vertex>();

            uint lCurrentFillStyle = 0;
            uint lCurrentLineStyle = 0;

            foreach (IRecord shapeRecord in lShapeRecordList)
            {
                if (shapeRecord is StyleChangedRecord)
                {
                    StyleChangedRecord record = (StyleChangedRecord)shapeRecord;

                    if (record.HasFillStyle0
                        && _FillStyleList.ContainsKey(record.FillStyle0))
                    {
                        _FillStyle0 = _FillStyleList[record.FillStyle0].Color;
                        lCurrentFillStyle = record.FillStyle0;
                    }
                    if (record.HasFillStyle1
                        && _FillStyleList.ContainsKey(record.FillStyle1))
                    {
                        _FillStyle1 = _FillStyleList[record.FillStyle1].Color;
                    }

                    if (record.HasLineStyle)
                    {
                        lCurrentLineStyle = record.LineStyle;
                    }

                    if (record.HasMove)
                    {
                        _Cursor = new Vector2(record.MoveDeltaX, record.MoveDeltaY);
                    }

                    if (record.HasNewStyles)
                    {
                        foreach (ILineStyle l in record.LineStyles.LineStyles)
                        {
                            _ParseLineStyle(l);
                        }

                        foreach (IFillStyle f in record.FillStyles.FillStyles)
                        {
                            _ParseFillStyle(f);
                        }

                        lCurrentFillStyle = (uint)_FillStyleList.Count;
                    }

                    //Generate Sub-Shape if there are any EdgeRecords

                    if (lShapePointArray.Count > 0)
                    {
                        List<short> indices = new List<short>();
                        short[] t = SWF.Decomposition.FlipcodeDecomposer.ConvexPartition(lShapePointArray);
                        if (t.Length < 1)
                        {
                            t = SWF.Decomposition.TimDecomposer.Decompose(lShapePointArray);
                        }
                        indices.AddRange(t);

                        IndexBuffer lIndexBuffer = new IndexBuffer(SWFMain.Device, IndexElementSize.SixteenBits, indices.Count, BufferUsage.None);
                        lIndexBuffer.SetData(indices.ToArray());

                        _ShapeIndexBufferList.Add(lIndexBuffer);

                        lShapePointArray.Clear();
                    }
                }
                else if (shapeRecord is StraightEdgeRecord)
                {
                    StraightEdgeRecord record = (StraightEdgeRecord)shapeRecord;
                    _Cursor += new Vector2(record.DeltaX, record.DeltaY);

                    lShapePointArray.Add(new Vertex
                    {
                        Position = _Cursor,
                    });
                }
                else if (shapeRecord is CurvedEdgeRecord)
                {
                    CurvedEdgeRecord record = (CurvedEdgeRecord)shapeRecord;

                    Vector2 control = _Cursor + new Vector2(record.ControlX, record.ControlY);
                    _Cursor += new Vector2(record.AnchorX + record.ControlX, record.AnchorY + record.ControlY);

                    lShapePointArray.Add(new Vertex
                    {
                        Position = _Cursor,
                    });

                    //Added tessalation via control point
                }
                else if (shapeRecord is EndShapeRecord)
                {
                    return;
                }
                else
                {
                    throw new Exception("Unsupported Shape Record.");
                }
            }
        }
        //----------------------------------------------------------------------
        private void _ParseLineStyle(ILineStyle lineStyle)
        {
            if (lineStyle is SWF.LineStyle)
            {
                SWF.LineStyle l = (SWF.LineStyle)lineStyle;

                _LineStyleList.Add((uint)(_LineStyleList.Count + 1), new LineStyle
                {
                    Color = new Color(l.Color.R, l.Color.G, l.Color.B, l.Color.A),
                    Width = l.Width,
                });
            }
            else if (lineStyle is LineStyle2)
            {
                LineStyle2 l2 = (LineStyle2)lineStyle;

                _LineStyleList.Add((uint)(_LineStyleList.Count + 1), new LineStyle
                {
                    Color = new Color(l2.Color.R, l2.Color.G, l2.Color.B, l2.Color.A),
                    Width = l2.Width,
                });
            }
            else
            {
                throw new Exception("Unknown line style type.");
            }
        }
        //----------------------------------------------------------------------
        private void _ParseFillStyle(IFillStyle lFillStyle)
        {
            switch (lFillStyle.FillType)
            {
                case FillType.Solid:
                {
                    SolidFill s = (SolidFill)lFillStyle;

                    _FillStyleList.Add((uint)(_FillStyleList.Count + 1), new FillStyle
                    {
                        Color = new Color(s.Color.R, s.Color.G, s.Color.B, s.Color.A)
                    });
                    break;
                }
                case FillType.Linear:
                case FillType.Radial:
                case FillType.Focal:
                {

                    break;
                }
                case FillType.ClippedBitmap:
                case FillType.RepeatingBitmap:
                case FillType.NSClippedBitmap:
                case FillType.NSRepeatingBitmap:
                {
                    BitmapFill b = (BitmapFill)lFillStyle;

                    //Do Stuff
                    break;
                }
                default:
                {
                    throw new Exception("Unsupported fill style type.");
                }
            }
        }

        #endregion
    }
}
